using System;
using System.Collections.Generic;
using System.Linq;
using uTILLIty.Rave.Contract.Configuration;
using uTILLIty.Rave.Contract.StorageAdapter;
using uTILLIty.Rave.Core.Configuration;

namespace uTILLIty.Rave.Core.Strategy
{
	/// <summary>
	/// Handles all validation of the configuration
	/// </summary>
  public class DefaultConfigurationValidatorStrategy : IConfigurationValidatorStrategy
	{

    /// <summary>
    /// Validates the complete configuration, returning any errors found
    /// </summary>
    public virtual string[] Validate(ApplicatorContext applicatorContext)
    {
      var errors = new List<string>(5);
      var configuration = applicatorContext.Configuration;
      var storageAdapter = applicatorContext.StorageAdapter;

      var duplicates = configuration.Members.GroupBy(m => m.ID).Where(c => c.Count() > 1);
      errors.AddRange(duplicates.Select(member => string.Format("Member ID '{0}' is not unique ({1} uses encountered", member.Key, member.Count())));

      var missingIDs = configuration.Members.Count(m => string.IsNullOrWhiteSpace(m.ID));
      if (missingIDs > 0)
        errors.Add(string.Format("{0} members have no ID property", missingIDs));

      errors.AddRange(storageAdapter.ValidateRootPath(configuration.RootDirectory));

      foreach (var member in configuration.Members)
        errors.AddRange(storageAdapter.ValidateMember(member));

      errors.AddRange(storageAdapter.ValidateAdapterConfiguration(configuration.StorageAdapterConfiguration));

      Action<ConfigurationExtensions.VisitingContext> processor = vc =>
      {
        var di = vc.DirectoryContext;
        EnsureMemberReferencesValid(di.Directory.SecurityEntries, configuration.Members, errors);
        EnsureGeneralMemberDataIsValid(di.Directory, errors);
	      ValidateDirectoryNameNotEmpty(di, errors);
        errors.AddRange(storageAdapter.ValidateDirectory(di));

        foreach (var secEntry in di.Directory.SecurityEntries)
          errors.AddRange(storageAdapter.ValidateSecurityEntry(secEntry));
      };

      configuration.VisitDirectories(processor, ve => ve.VisitChildren = true);

	    var membersWhichShouldExist = configuration.Members
				.Where(m => m.CreationConfiguration == null || m.CreationConfiguration.Data == null);
	    EnsureNonCreateMembersExist(membersWhichShouldExist, storageAdapter, errors);

      return errors.ToArray();
    }

		/// <summary>
		/// Traverses all supplied members and ensures they exist (they should, as each has no creation-information)
		/// </summary>
		protected virtual void EnsureNonCreateMembersExist(IEnumerable<Member> members, IStorageAdapter storageAdapter, List<string> errors)
		{
			foreach (var member in members)
			{
				var target = storageAdapter.TryResolveMember(member);
				if (target != null)
					continue;
				errors.Add(string.Format(Properties.TraceMessages.NonCreationMemberNotFound, member));
			}
		}


		/// <summary>
		/// Ensures that the directory name is not empty
		/// </summary>
		protected virtual void ValidateDirectoryNameNotEmpty(DirectoryContext directory, List<string> errors)
		{
			if (!string.IsNullOrWhiteSpace(directory.Directory.Name))
				return;

			errors.Add(string.Format(Properties.Resources.DirectoryNameEmpty, directory));
		}

		/// <summary>
    /// Validates any <see cref="IStorageAdapter"/> agnostic configuration data
    /// </summary>
    /// <param name="directory">the directory to validate</param>
    /// <param name="errors">any errors found</param>
		protected virtual void EnsureGeneralMemberDataIsValid(Directory directory, List<string> errors)
    {
      foreach (var securityEntry in directory.SecurityEntries.Where(se => se.DontAddToAcl))
      {
        if (!string.IsNullOrWhiteSpace(securityEntry.Rights))
        {
          errors.Add(string.Format(Properties.TraceMessages.DontAddToAclsButRightsSpecified,
                                   securityEntry.MemberID, directory.Name));
        }
      }
    }

		/// <summary>
		/// Checks if all references to members are valid
		/// </summary>
		/// <param name="securityEntries">the ACLs to validate</param>
		/// <param name="members">a list of all members defined</param>
		/// <param name="errors">any errors found</param>
    protected virtual void EnsureMemberReferencesValid(SecurityEntry[] securityEntries, Member[] members, List<string> errors)
    {
      var resolvedMembers = new HashSet<string>();
      foreach (SecurityEntry securityEntry in securityEntries.Where(se => !resolvedMembers.Contains(se.MemberID)))
      {
        Member member;
				switch (securityEntry.MemberType)
				{
					case MemberType.Unknown:
						member = members.FirstOrDefault(m => m.ID.Equals(securityEntry.MemberID, StringComparison.CurrentCultureIgnoreCase));
						break;
					case MemberType.User:
						member = members.FirstOrDefault(m => m.MemberType == MemberType.User && m.ID.Equals(securityEntry.MemberID, StringComparison.CurrentCultureIgnoreCase));
						break;
					case MemberType.Group:
						member = members.FirstOrDefault(m => m.MemberType == MemberType.Group && m.ID.Equals(securityEntry.MemberID, StringComparison.CurrentCultureIgnoreCase));
						break;
					default:
						throw new ArgumentOutOfRangeException();
				}
        
        if (member != null)
          resolvedMembers.Add(securityEntry.MemberID);
      }

      errors.AddRange(securityEntries.Where(e => !resolvedMembers.Contains(e.MemberID))
				.Select(entry => string.Format(Properties.ExceptionMessages.MemberNotFound, entry.MemberID)));
    }
  }
}